/**
 *
 * The MIT License
 *
 * Copyright (c) 2008 Board of Trustees, Leland Stanford Jr. University. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.macdadi.ks.client.views;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.*;

import org.macdadi.core.client.auth.AuthService;
import org.macdadi.core.client.auth.AuthenticatedUser;
import org.macdadi.core.client.auth.AuthorizationException;
import org.macdadi.core.client.domain.*;
import org.macdadi.core.client.ui.EditableTree;
import org.macdadi.core.client.domain.DataService;
import org.macdadi.core.client.domain.DataWriteService;
import org.macdadi.ks.client.View;
import org.macdadi.ks.client.ViewContainer;

import java.util.ArrayList;
import java.util.HashMap;  
import com.gwtext.client.widgets.Panel;  
import com.gwtext.client.widgets.TabPanel;
import com.gwtext.client.widgets.layout.AccordionLayout;
import com.gwtext.client.widgets.layout.BorderLayout;
import com.gwtext.client.widgets.layout.BorderLayoutData;
import com.gwtext.client.widgets.layout.HorizontalLayout;  
import com.gwtext.client.widgets.layout.VerticalLayout;  
import com.gwtext.client.widgets.tree.TreeNode;  
import com.gwtext.client.widgets.tree.TreePanel;  
import com.gwtext.client.core.EventObject;   
import com.gwtext.client.core.RegionPosition;
import com.gwtext.client.widgets.Button;  
import com.gwtext.client.widgets.event.ButtonListenerAdapter;  
import com.gwtext.client.core.Ext;
import com.gwtext.client.core.ExtElement;
import com.gwtext.client.widgets.Window;

/**
 * @author Ben Suter <ben.suter@gmail.com>
 */
public class Impacts extends View implements ClickListener
{
	protected FlexTable table;
	protected ProjectInfo project;
	protected boolean allowUpdate = false;
	protected TreeNode[] checkedGoalNodes;
	protected TreeNode[] checkedOptionNodes;
	Button buttonCreateImpact;
	Button ReSelectImpacts;
	protected VerticalPanel chartPanel;
	protected EditableTree tree;
	protected EditableTree tree_options;
	protected ArrayList expandedItems = new ArrayList();
	protected AuthenticatedUser currentUser;
	protected VerticalPanel mainPanel = new VerticalPanel();
	Panel accordionPanel;
	Panel westPanel;
	private static String instructionsString;
	private static String bugsString;
	Button instructionsButton;

	public static ViewInfo init()
	{
		String info = "<p>Evaluate the impact of each option on each goal</p>";

		return new ViewInfo("Impacts", info)
		{
			public View createInstance(ViewContainer container)
			{
				return new Impacts(container);
			}

			public String getColor()
			{
				return "#71C671";
			}
		};
	}

	public Impacts(ViewContainer container)
	{
		this.container = container;
		project = container.getCurrentProject();
		table = new FlexTable();

		mainPanel.setStyleName("macdadi-Impacts");
		mainPanel.setWidth("98%");
		mainPanel.setHeight("98%");

		Panel space = new Panel();
		space.setHeight(20);
		space.setBorder(false);

		instructionsString = "<p> On this page, the user is allowed to do several things: </p>" +
		"<p> ~~~ </p> " +
		"<p><b>   1) Choose which goals to go in the Impacts Matrix </b></p>" +
		"<p><b>   2) Choose which options to go in the Impacts Matrix</b></p>" +
		"<p><b>   3) Click the Generate Matrix button to create the desired matrix and then edit its impact values </b></p>" +
		"<p> ~~~ </p> " +
		"<p> Remarks on the first two bullets - By giving the user the option of choosing goals and options, this allows the user to create their desired matrix and it also solves scalability issues. </p>" +
		"<p> Remarks on the third bullet - Here a matrix is generated and upon clicking each cell a widget appears that allows the user to choose a value between -3 and 3, inclusive. Depending on the impact value the cell is color coded either red, green, or white. To reset the matrix and unmask the accordion panel simply click the Reset button. At that time the user is allowed to recustomize their next impact matrix.</p>";
		bugsString = "<p> None ATM </p>" +
		"<p> ~~~ </p>" +
		"<p> Discover any bugs? Please contact me at: <i>jwei512@stanford.edu</i> </p>";

		HTML instructionsHTML = new HTML(instructionsString);

		TabPanel tabPanel = new TabPanel();  
		tabPanel.setActiveTab(0);

		Panel tab1 = new Panel();  
		tab1.setTitle("Instructions");  
		tab1.add(instructionsHTML);
		tab1.setAutoScroll(true);  

		Panel tab2 = new Panel();  
		tab2.setTitle("Current Bugs/Fixes");  
		tab2.setHtml(bugsString);  
		tab2.setAutoScroll(true);

		tabPanel.add(tab1);
		tabPanel.add(tab2);

		BorderLayoutData centerData = new BorderLayoutData(RegionPosition.CENTER);  
		centerData.setMargins(3, 0, 3, 3);

		final Window window = new Window();
		window.setTitle("Instructions for Impacts Tab");  
		window.setClosable(true);  
		window.setWidth(600);  
		window.setHeight(350);  
		window.setPlain(true); 
		window.setLayout(new BorderLayout());  
		window.add(tabPanel, centerData);
		window.setCloseAction(Window.HIDE);

		instructionsButton = new Button("Instructions");  
		instructionsButton.addListener(new ButtonListenerAdapter() {
			public void onClick(Button button, EventObject e) {
				window.show();
			}  
		});

		mainPanel.add(instructionsButton);
		mainPanel.add(space);

		initWidget(mainPanel);
	}

	public void onShow()
	{
		allowUpdate = container.getCurrentUser().isAuthorizedOperation(project.getId(), AuthService.TARGET_IMPACTS, AuthService.OPERATION_UPDATE);

		accordionPanel = createAccordionPanel();

		final Panel horizontalPanel = new Panel();  
		horizontalPanel.setLayout(new HorizontalLayout(20));  

		Panel verticalPanel = new Panel();  
		verticalPanel.setLayout(new VerticalLayout(15));

		westPanel = new Panel();
		westPanel.add(accordionPanel);
		westPanel.setTitle("Select desired goals and options.");

		//TODO: MAKE IT COLLAPSE LEFT?
		BorderLayoutData westData = new BorderLayoutData(RegionPosition.WEST);  
		westData.setSplit(true);

		horizontalPanel.add(westPanel, westData);
		horizontalPanel.add(table);
		verticalPanel.add(horizontalPanel);
		verticalPanel.addButton(ReSelectImpacts);
		
		mainPanel.add(verticalPanel);
	}

	public void onHide()
	{
		for(int i = table.getRowCount()-1; i >= 0; i--) {
			table.removeRow(i);
		}
		accordionPanel.clear();
		accordionPanel.destroy();
		mainPanel.clear();
		allowUpdate = false;
	}

	private Panel createAccordionPanel() {
		Panel accordionPanel = new Panel();
		accordionPanel.setLayout(new AccordionLayout(true));
		accordionPanel.setId("accordion-impacts");
		accordionPanel.setHeight(500);
		accordionPanel.setWidth(325);
		accordionPanel.setAnimCollapse(true);

		final TreePanel treeGoals = new SampleGoalsTree();
		treeGoals.setId("tree-goals");
		treeGoals.setTitle("Goals");
		treeGoals.setWidth(325);  
		treeGoals.setHeight(500);
		treeGoals.setContainerScroll(true);
		treeGoals.setAutoScroll(true);
		treeGoals.setRootVisible(true);

		final TreePanel treeOptions = new SampleOptionsTree();
		treeOptions.setId("tree-options");
		treeOptions.setTitle("Options");
		treeOptions.setWidth(325);  
		treeOptions.setHeight(500);
		treeOptions.setContainerScroll(true);
		treeOptions.setAutoScroll(true);
		treeOptions.setRootVisible(true);

		buttonCreateImpact = new Button("Create Options-Impacts Matrix");
		buttonCreateImpact.setEnableToggle(true);		

		ReSelectImpacts = new Button("Reset");
		ReSelectImpacts.setEnableToggle(true);
		ReSelectImpacts.disable();	

		treeOptions.addButton(buttonCreateImpact);

		buttonCreateImpact.addListener(new ButtonListenerAdapter() {
			public void onClick(Button button, EventObject e) {
				checkedOptionNodes = treeOptions.getChecked();
				checkedGoalNodes = treeGoals.getChecked();
				final ExtElement elm1 = Ext.get("accordion-impacts");
				elm1.mask("GENERATING MATRIX");
				button.toggle(true);
				ReSelectImpacts.toggle(false);
				ReSelectImpacts.enable();
				populateTable();
			}
		});

		ReSelectImpacts.addListener(new ButtonListenerAdapter() {  
			public void onClick(Button button, EventObject e) {
				for(int i = table.getRowCount()-1; i >= 0; i--) {
					table.removeRow(i);
				}
				final ExtElement elm1 = Ext.get("accordion-impacts");
				elm1.unmask();
				checkedOptionNodes = null;
				checkedGoalNodes = null;
				button.toggle(true);
				buttonCreateImpact.toggle(false);
				button.disable();
			}
		});

		accordionPanel.add(treeGoals);
		treeGoals.collapse();
		accordionPanel.add(treeOptions);
		
		return accordionPanel;
	}

	class SampleGoalsTree extends TreePanel {  

		public SampleGoalsTree() {  

			final TreeNode root = new TreeNode("Goals");
			root.setExpanded(true);

			DataService.App.getInstance().getGoals(project.getId(), container.getCurrentAuthToken(), new AsyncCallback() {
				public void onFailure(Throwable caught) {
					GWT.log("Failed to populate the goals tree", caught);
					if (caught instanceof AuthorizationException) {
						root.setText("Please login to view goals for " + project.getName());
					}
				}

				public void onSuccess(Object result) {
					root.setText("Goals");

					final Goal[] roots = (Goal[]) result;
					for (int i = 0; i < roots.length; i++) {
						addChildGoal(root,roots[i],true, roots[i].getNumChildren());
					}

					/* Expand the first and second levels */
					root.setExpanded(true);
					root.expandChildNodes(false);
				}
			});

			setTitle("Goals for Green Dorm Project");  
			setWidth(200);  
			setHeight(400);  
			setRootNode(root);
		}  
	}

	class SampleOptionsTree extends TreePanel {  

		public SampleOptionsTree() {  

			final TreeNode root = new TreeNode("Options");
			root.setExpanded(true);

			DataService.App.getInstance().getOptions(project.getId(), container.getCurrentAuthToken(), new AsyncCallback() {
				public void onFailure(Throwable caught) {
					GWT.log("Failed to populate the options tree", caught);
					if (caught instanceof AuthorizationException) {
						root.setText("Please login to view options for " + project.getName());
					}
				}

				public void onSuccess(Object result) {
					root.setText("Options");

					Option[] roots = (Option[]) result;
					for (int i = 0; i < roots.length; i++) {
						addChildOption(root,roots[i],true, roots[i].getNumChildren());
					}

					/* Expand the first and second levels */
					root.setExpanded(true);
					root.expandChildNodes(false);
				}
			});

			setTitle("Options for Green Dorm Project");  
			setWidth(200);  
			setHeight(400);  
			setRootNode(root);
		}
	}

	protected void addChildOption(TreeNode parent, Option option, boolean recurse, int leaf) {
		TreeNode node = new TreeNode(option.getName());
		node.setUserObject(option);
		if(leaf == 0) node.setChecked(true);
		node.setId(Integer.toString(option.getId()).trim());
		parent.appendChild(node);

		if (recurse) {
			for (int i = 0; i < option.getNumChildren(); i++) {
				addChildOption(node, option.getChild(i), true, option.getChild(i).getNumChildren());
			}
		}

		/* If this node, as identified by its option's id, was "expanded" in a
		 * previous incarnation of this tree, then expand it here again.
		 */
		if (expandedItems.contains(Integer.toString(option.getId())))
			node.setExpanded(true);
	}

	protected void addChildGoal(TreeNode parent, Goal goal, boolean recurse, int leaf) {
		TreeNode node = new TreeNode(goal.getName());
		node.setUserObject(goal);
		if(leaf == 0) node.setChecked(true);
		parent.appendChild(node);

		if (recurse) {
			for (int i = 0; i < goal.getNumChildren(); i++) {
				addChildGoal(node, goal.getChild(i), true, goal.getChild(i).getNumChildren());
			}
		}

		/* If this node, as identified by its option's id, was "expanded" in a
		 * previous incarnation of this tree, then expand it here again.
		 */
		if (expandedItems.contains(Integer.toString(goal.getId())))
			node.setExpanded(true);
	}

	protected void populateTable()
	{
		project = container.getCurrentProject();

		DataService.App.getInstance().getImpacts(project.getId(), container.getCurrentAuthToken(), new AsyncCallback()
		{
			public void onFailure(Throwable caught)
			{
				GWT.log("Failed to populate the impacts table", caught);
				if (caught instanceof AuthorizationException) {
					// TODO: do not use Window.alert for error feedback
					System.out.println("Please login to view goals for " + project.getName());
				}
			}

			public void onSuccess(Object result)
			{
				ImpactMatrix matrix = (ImpactMatrix) result;

				for (int i = 0; i < checkedOptionNodes.length; i++) {
					table.setText(0,i+1, checkedOptionNodes[i].getText());
					table.getFlexCellFormatter().setStyleName(0, i + 1, "impact-option-header");
				}

				for (int i = 0; i < checkedGoalNodes.length; i++) {
					table.setText(i+1, 0, checkedGoalNodes[i].getText());
					table.getFlexCellFormatter().setStyleName(i + 1, 0, "impact-goal-header");
				}

				Option[] optionsArray = getOptions(checkedOptionNodes, matrix);
				WeightedGoal[] goalsArray = getGoals(checkedGoalNodes, matrix);

				WeightedGoal g;
				for (int i = 0; i < goalsArray.length; i++) {
					g = goalsArray[i];
					for ( int j = 0; j < optionsArray.length; j++) {
						setImpactTableCell(i + 1, j + 1, matrix.getImpact(g, optionsArray[j]));
					}
				}
			}
		});
	}

	public Option[] getOptions(TreeNode[] checkedOptionNodes, ImpactMatrix matrix) {
		//ArrayList<Option> options = new ArrayList<Option>();
		//For some reason the ArrayList did not work.

		Option[] tempOptions = matrix.getOptions();
		Option[] options = new Option[0];

		for(int i = 0; i < checkedOptionNodes.length; i++) {
			for(int j = 0; j < tempOptions.length; j++) {
				if(checkedOptionNodes[i].getText().trim().equals(tempOptions[j].getName().trim())) {
					Option[] optionsDelete = new Option[options.length+1];
					for(int k = 0; k < options.length; k++) {
						optionsDelete[k] = options[k];
					}
					optionsDelete[options.length] = tempOptions[j];
					options = optionsDelete;
				}
			}
		}
		return  options;
	}

	public WeightedGoal[] getGoals(TreeNode[] checkedGoalNodes, ImpactMatrix matrix) {
		WeightedGoal[] tempGoals = matrix.getGoals();
		WeightedGoal[] goals = new WeightedGoal[0];

		for(int i = 0; i < checkedGoalNodes.length; i++) {
			for(int j = 0; j < tempGoals.length; j++) {
				if(checkedGoalNodes[i].getText().trim().equals(tempGoals[j].getName().trim())) {
					WeightedGoal[] goalsDelete = new WeightedGoal[goals.length+1];
					for(int k = 0; k < goals.length; k++) {
						goalsDelete[k] = goals[k];
					}
					goalsDelete[goals.length] = tempGoals[j];
					goals = goalsDelete;
				}
			}
		}
		return  goals;
	}

	// TODO: table sorting: click on option, sort so that values asc/desc for that option
	// TODO: table sorting: click on goal, sort so that values asc/desc L-R for that goal (re-order columns)

	protected void setImpactTableCell(int row, int col, Impact impact)
	{
		// improve this, use CSS class instead of straight color mapping here in code
		// also, apply to entire cell, not just to span of content

		table.setWidget(row, col, new ImpactCell(impact, this));
		table.getFlexCellFormatter().setStyleName(row, col, getValueStyle(impact.getValue()));
	}

	protected String getValueColor(int value) {
		switch (value) {
		case 0:
			return "#ffffff";
		case -1:
		case -2:
		case -3:
			return "#CD5555";
		case 1:
		case 2:
		case 3:
			return "#9AFF9A";
		default:
			return "#ffffff";
		}        
	}

	protected String getValueStyle(int value) {
		switch (value) {
		case 0:
			return "impact-zero";
		case -1:
			return "impact-negative-1";
		case -2:
			return "impact-negative-2";
		case -3:
			return "impact-negative-3";
		case 1:
			return "impact-positive-1";
		case 2:
			return "impact-positive-2";
		case 3:
			return "impact-positive-3";
		default:
			return "";
		}
	}

	public void onClick(Widget sender) {
		if ( ! allowUpdate )
			return;

		if (sender instanceof ImpactCell) {
			if (cellEditPanel == null) {
				cellEditPanel = new ImpactCellEditor();
			}

			ImpactCell cell = (ImpactCell) sender;
			cellEditPanel.editImpact(cell.getImpact(), cell.getAbsoluteLeft() + 30, cell.getAbsoluteTop());
		}
	}

	protected ImpactCellEditor cellEditPanel;

	class ImpactCellEditor extends DialogBox implements AsyncCallback
	{
		protected Impact impact;
		protected ListBox choices;

		public ImpactCellEditor() {
			super(true, true);
			setText("Define the Impact");

			/* A panel for editing the impact value */
			VerticalPanel vp = new VerticalPanel();

			choices = new ListBox();
			for ( int i = 3; i > -4; i-- ) {
				choices.addItem(Integer.toString(i));
			}
			vp.add(choices);
			Button btnAdd = new Button("Apply", new ButtonListenerAdapter() {  
				public void onClick(Button button, EventObject e) {  
					int newValue;
					newValue = Integer.parseInt(choices.getValue(choices.getSelectedIndex()));
					updateImpact(newValue); 
				}
			});	
			vp.add(btnAdd);

			setWidget(vp);    
		}

		public void editImpact(Impact impact, int x, int y) {
			this.impact = impact;
			choices.setSelectedIndex(3 - impact.getValue());
			setPopupPosition(x + 30, y);
			show();
		}

		public void stopEditing() {
			hide();
			impact = null;
		}

		protected void updateImpact(int newValue) {
			if ( impact == null )
				return;

			// TODO: implement in full
			//Window.alert("Changing value to " + newValue + " for goal " + impact.getGoal().getId() + " and option " + impact.getOption().getId());

			DataWriteService.App.getInstance().updateImpact(project.getId(), impact.getGoal().getId(), impact.getOption().getId(), newValue, container.getCurrentAuthToken(), this);
		}

		/**
		 * Displays an error message
		 *
		 * @param caught  the exception
		 */
		public void onFailure(Throwable caught) {
			// TODO: display a message to the user
			GWT.log("The desired change did not succeed. A problem occurred: " + caught.getMessage(), caught);
		}

		/*
		 * In the case of success, the editor is closed and the onSuccess command
		 * is executed. Otherwise we log an error.
		 *
		 * @param result true if the impact was updated successfully
		 */
		public void onSuccess(Object result) {
			if (((Boolean) result).booleanValue()) {
				stopEditing();
				table.clear();
				populateTable();
			} else {
				// TODO: show feedback to user
				GWT.log("The desired change did not succeed", null);
			}
		}

	}

	class ImpactCell extends HTML
	{
		protected Impact impact;

		public ImpactCell(Impact impact, ClickListener listener) {
			super();
			this.impact = impact;
			buildSelf();
			addClickListener(listener);
		}

		protected void buildSelf() {
			setHTML("<center><b>" + Integer.toString(impact.getValue()) + "</b></center>");
		}

		public Impact getImpact() {
			return impact;
		}
	}
}